﻿#define _CRT_SECURE_NO_WARNINGS
#include"Snake.h"
void Set_Pos(short x,short y)
{
    HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    //设置光标位置：
    COORD pos = { x,y };
    SetConsoleCursorPosition(houtput, pos);
}
void WelcomeToGame()
{
    //欢迎界面：
    Set_Pos(35, 10);
    printf("欢迎来到贪吃蛇游戏\n");
    Set_Pos(36,15);
    system("pause");//这里会打印出请按任意键继续
    system("cls");//清理界面

    //玩法介绍界面：
    Set_Pos(25, 10);
    printf("用↑ ↓ ← → 键来控制蛇的移动方向，按F3加速，F4减速");
    Set_Pos(30, 11);
    printf("加速可以获得更高的分数");
    Set_Pos(36, 15);
    system("pause");
    system("cls");
}
void Map()
{
    int i = 0;
    //上边墙：
    for (i = 0; i <29; i++){
        wprintf(L"%lc",WALL);
    }
    //下边墙：
    Set_Pos(0, 26); //从第1列第26行开始打印
    for (i = 0; i < 29; i++){
        wprintf(L"%lc", WALL);
    }
    //左边墙：
    for (i = 1; i <= 25; i++){
        Set_Pos(0, i);
        wprintf(L"%lc", WALL);
    }
    //右边墙：
    for (i = 1; i <= 25; i++) {
        Set_Pos(56, i);
        wprintf(L"%lc", WALL);
    }
}
void InitSnake(SnakeGame*Snake)
{
    SnakeNode* cur = NULL;
    int i = 0;

    //蛇的身体一开始有五节：
    for (i = 0; i < 5; i++)
    {
        cur = (SnakeNode*)malloc(sizeof(SnakeNode));
        if (cur == NULL) {
            perror("malloc:");
            return;
        }
        cur->x =POS_x + 2 * i;
        cur->y = POS_y;
        cur->next = NULL;
        if (Snake->SnakeHead == NULL) {
            Snake->SnakeHead = cur;
        }
        else {
            cur->next = Snake->SnakeHead;
            Snake->SnakeHead = cur;
        }
    }

    //打印最初蛇的位置：
    cur = Snake->SnakeHead;
    while (cur) {
        Set_Pos(cur->x,cur->y);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }
    
    //设置贪吃蛇游戏的属性：
    Snake->dir = RIGHT;//初始运动方向为向右
    Snake->condition = NORMAL;//蛇的状态
    Snake->Food_score = 10;//每个食物的分数
    Snake->Score = 0;//总分
    Snake->Speed = 200;//休眠时间
}
void CreateFood(SnakeGame*Snake)
{
    int x;
    int y;
    again:
    do
    {
        //食物必须在墙内：
        x = rand() %53 +2;  //2~54
        y = rand() %25 +1;  //1~25

    } while (x % 2 != 0);//该条件使得食物的坐标的x必须是2的倍数，因为蛇的每个节点的x都是2的倍数
                         //如果食物的x是奇数，那么蛇是吃不到食物的，因为蛇头的x（偶数）不可能等于食物的x（奇数）

    //食物不能与蛇身重合：
    SnakeNode* cur = Snake->SnakeHead;
    while (cur) {
        if (x == cur->x && y == cur->y) {
            goto again;
        }
        cur = cur -> next;
    }

    //创建食物：
    SnakeNode* NewFood = (SnakeNode*)malloc(sizeof(SnakeNode));
    if (NewFood == NULL) {
        perror("CreateFood()::malloc:");
        return;
    }
    NewFood->x = x;
    NewFood->y = y;
    NewFood->next = NULL;
    Snake->Food = NewFood;
    
    //打印食物：
    Set_Pos(x, y);
    wprintf(L"%lc", FOOD);
}
void InitSnakeGame(SnakeGame*Snake)
{
     //1.设置窗口大小和名字
    system("mode con cols=100 lines=35");
    system("title 贪吃蛇");

     //2.隐藏光标
    HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取当前控制台 houtout为句柄，本质是void*指针
    CONSOLE_CURSOR_INFO  CursorInfo;//创建控制台的光标的结构体
    GetConsoleCursorInfo(houtput, &CursorInfo);//将当前控制台光标的信息存放在CursorInfo结构体中
    CursorInfo.bVisible = false;//隐藏控制台的光标
    SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标的状态

    //3.打印欢迎界面及玩法介绍：
    WelcomeToGame();

    //4.地图绘制：
     Map();
    
    //5.初始化蛇：
    InitSnake(Snake);
    
    //6.创建食物：
    CreateFood(Snake);
}
void Print_Help_Info()
{
    Set_Pos(64, 16);
    wprintf(L"%ls",L"不能穿墙，不能咬到自己");
    Set_Pos(64, 17);
    wprintf(L"%ls",L"用↑ ↓ ← → 键来控制蛇的移动方向");
    Set_Pos(64, 18);
    wprintf(L"%ls",L"按F3加速，F4减速");
    Set_Pos(64, 19);
    wprintf(L"%ls",L"按ESC退出游戏,按空格暂停");
}
void Pause()
{
    while (1)//暂停游戏的逻辑就是让程序一直休眠，直到需要继续游戏
    {
        Sleep(200);
        if (KEY_PRESS(VK_SPACE)) {//再按空格停止休眠
            break;
        }
    }
}
void EatFood(SnakeNode*NextNode,SnakeGame* Snake)
{
    //将食物头插进蛇：
    Snake->Food->next= Snake->SnakeHead;
    Snake->SnakeHead = Snake->Food;

    //释放新创建的节点：
    free(NextNode);
    NextNode = NULL;

    //打印蛇：此时蛇已经变长
    SnakeNode* cur = Snake->SnakeHead;
    while (cur) {
        Set_Pos(cur->x, cur->y);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }

    //加分：
    Snake->Score += Snake->Food_score;

    //创建新的食物：
    CreateFood(Snake);

}
void NoFood(SnakeNode* NextNode,SnakeGame*Snake)
{
    //将下一个节点头插进蛇：
    NextNode->next = Snake->SnakeHead;
    Snake->SnakeHead = NextNode;

    //打印新的蛇并找到原来蛇的尾节点：
    SnakeNode* cur = Snake->SnakeHead;
    while (cur->next->next)
    {
        Set_Pos(cur->x, cur->y);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }

    //删除原来蛇的尾节点并将其图案变成两个空格，否则蛇会一直变长
    Set_Pos(cur->next->x, cur->next->y);
    printf("  ");
    free(cur->next);
    cur->next = NULL;
}
void SnakeMove(SnakeGame* Snake)
{
    //蛇运动的逻辑是：先创建一个新的节点，该节点的坐标根据蛇将要运动的方向来确定，如果
     //该节点恰好与食物重合，就执行吃食物的逻辑，否则执行不吃食物的逻辑

    SnakeNode* NextNode = (SnakeNode*)malloc(sizeof(SnakeNode));
    if (NextNode == NULL) {
        perror("SnakeMove()::malloc:");
        return;
    }
    switch(Snake->dir)//根据蛇将要往哪个方向运动来确定下一个节点的坐标
    {
    case UP:
        NextNode->x = Snake->SnakeHead->x;
        NextNode->y = Snake->SnakeHead->y - 1;
        break;
    case DOWN:
        NextNode->x = Snake->SnakeHead->x;
        NextNode->y = Snake->SnakeHead->y + 1;
        break; 
    case LEFT:
        NextNode->x = Snake->SnakeHead->x - 2; //注意新节点的x坐标必须是偶数
        NextNode->y = Snake->SnakeHead->y;
        break; 
    case RIGHT:
        NextNode->x = Snake->SnakeHead->x + 2;
        NextNode->y = Snake->SnakeHead->y;
        break;
    }

    //吃到食物：
    if (NextNode->x == Snake->Food->x&& NextNode->y == Snake->Food->y) {
         EatFood(NextNode,Snake);
    }
    //不是食物：
    else {
        NoFood(NextNode,Snake);
    }

}
void Kill_By_Wall(SnakeGame* Snake)
{
    //当蛇头的坐标与墙的坐标重合时，就是撞到墙了
    if (Snake->SnakeHead->x == 0 || Snake->SnakeHead->x == 56 ||
        Snake->SnakeHead->y == 0 || Snake->SnakeHead->y == 26) {
        Snake->condition = KILL_BY_WALL;//状态发生改变
    }
}
void Kill_By_Self(SnakeGame* Snake)
{
    //当蛇头的坐标与蛇身的坐标重合时，就是咬到自己了
    SnakeNode* cur = Snake->SnakeHead->next;
    while (cur) {
        if (cur->x == Snake->SnakeHead->x && cur->y == Snake->SnakeHead->y) {
            Snake->condition = KILL_BY_SELF;//状态发生改变
            break;
        }
        cur = cur->next;
    }
}
void GameRun(SnakeGame* Snake)//游戏运行的逻辑是：蛇根据按键的情况来进行运动，由于蛇是不断运动的
{                             //所以用do while()循环来控制
    Print_Help_Info();
    do
    {
        //当印当前总分及分值：
        Set_Pos(66, 13);
        printf("总分：%d   食物分值：%2d", Snake->Score,Snake->Food_score); //%2d的2是为了防止从两位数变成
                                                                           //一位数时，第二位的数字未被覆盖
        //检测按键：

        if (KEY_PRESS(VK_UP) && Snake->dir != DOWN) {//想往上走时，蛇就不能正在往下走，                                            
            Snake->dir = UP;                          //否则吃到自己
        }
        else if (KEY_PRESS(VK_DOWN) && Snake->dir != UP) {//想往上走时，蛇就不能正在往下走，
            Snake->dir = DOWN;
        }
        else if (KEY_PRESS(VK_LEFT) && Snake->dir != RIGHT) {//想往上走时，蛇就不能正在往下走，
            Snake->dir = LEFT;
        }
        else if (KEY_PRESS(VK_RIGHT) && Snake->dir != LEFT){ //想往上走时，蛇就不能正在往下走，
            Snake->dir = RIGHT;
        }
        else if (KEY_PRESS(VK_F3)&&Snake->Speed>80) {//加速
            Snake->Speed -= 30;//休眠时间减少
            Snake->Food_score += 2;//每个食物的分值增加
        }
        else if (KEY_PRESS(VK_F4)&&Snake->Food_score>2) {
            Snake->Speed += 30;//休眠时间增加
            Snake->Food_score -= 2;//每个食物的分值减少
        }
        else if (KEY_PRESS(VK_ESCAPE)) {//退出游戏(正常退出)
            Snake->condition = END_NORMAL;
        }
        else if (KEY_PRESS(VK_SPACE)) {//暂停
            Pause();
        }

        //蛇移动：
        SnakeMove(Snake);

        //判断是否撞墙或者吃到自己：
        Kill_By_Wall(Snake);
        Kill_By_Self(Snake);

        //休眠：
        Sleep(Snake->Speed);  //程序在这里暂停Speed毫秒再继续运行

    } while (Snake->condition == NORMAL);//只有当蛇的状态为正常时，循环才会继续
}
void GameEnd(SnakeGame* Snake)
{
    //游戏从运行逻辑处退出后，根据蛇的状态来判断游戏是以什么方式结束
    switch (Snake->condition)
    {
    case END_NORMAL:
        Set_Pos(24, 12);
        printf("您退出了游戏\n");
        break;
    case KILL_BY_WALL:
        Set_Pos(24, 12);
        printf("撞到了墙，游戏结束\n");
        break;
    case KILL_BY_SELF:
        Set_Pos(24, 12);
        printf("咬到了自己，游戏结束\n");
        break; 
    }

    //释放蛇的节点空间：
    SnakeNode* Next = Snake->SnakeHead->next;
    while (Snake->SnakeHead) {
        free(Snake->SnakeHead);
        Snake->SnakeHead = Next;
        if (Next != NULL) {
            Next = Snake->SnakeHead->next;
        }
    }
}

